package com.ml.loan.common.aspect;

import com.alibaba.fastjson.JSONObject;
import com.google.common.collect.Maps;
import com.ml.loan.common.annotaion.BusinessLock;
import com.ml.loan.common.constants.CommonConstant;
import com.ml.loan.common.exception.BusinessException;
import com.ml.loan.common.lock.DistributedLock;
import com.ml.loan.common.lock.ZLock;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;

import java.util.Arrays;
import java.util.List;
import java.util.Map;

/**
 * 分布式锁切面
 * @author Mickey
 * @date 2022/2/18 19:37
 */
@Slf4j
@Aspect
@Component
public class LockAspect {

    @Autowired(required = false)
    private DistributedLock distributedLock;

    /**
     * SpEL表达式解析
     */
    private SpelExpressionParser parser = new SpelExpressionParser();
    /**
     * 用于获取方法形参名
     */
    private DefaultParameterNameDiscoverer discoverer = new DefaultParameterNameDiscoverer();

    @Around("@annotation(businessLock)")
    public Object aroundLock(ProceedingJoinPoint point, BusinessLock businessLock) throws Throwable {
        String lockKey = businessLock.spelKey();
        if (distributedLock == null) {
            throw new BusinessException("DistributedLock is null");
        }
        if (StringUtils.isBlank(lockKey)) {
            throw new BusinessException("lockKey is null");
        }
        MethodSignature methodSignature = (MethodSignature) point.getSignature();
        String methodKey = methodSignature.getDeclaringTypeName() + CommonConstant.SPOT + methodSignature.getName();
        //是否包含springEL表达式
        if (lockKey.contains("#")) {
            //key = 计算SpEL表达式的值
            lockKey = getValBySpEL(lockKey, methodSignature, point.getArgs());
            if (StringUtils.isBlank(lockKey)) {
                // SpEL表达式计算失败不加锁
                return point.proceed();
            }
        }
        lockKey = methodKey + CommonConstant.SPOT + lockKey;
        ZLock lock = null;
        try {
            if (businessLock.waitTime() > 0) {
                //等待时间大于0，使用tryLock尝试加锁，否则使用lock加锁
                lock = distributedLock.tryLock(lockKey, businessLock.waitTime(), businessLock.leaseTime(), businessLock.unit(), businessLock.isFair());
            } else {
                lock = distributedLock.lock(lockKey, businessLock.leaseTime(), businessLock.unit(), businessLock.isFair());
            }
            if (lock != null) {
                return point.proceed();
            } else {
                throw new BusinessException("数据处理中，请稍后再试...");
            }
        } finally {
            //最终需要解锁
            distributedLock.unlock(lock);
        }
    }

    /**
     * 根据参数解析SpEL表达式的值
     *
     * @param spEL
     * @param methodSignature
     * @param args
     * @return
     */
    private String getValBySpEL(String spEL, MethodSignature methodSignature, Object[] args) {
        Map<String, Object> params = Maps.newHashMap();
        try {
            //获取方法形参名数组
            String[] paramNames = discoverer.getParameterNames(methodSignature.getMethod());
            if (paramNames != null && paramNames.length > 0) {
                Expression expression = parser.parseExpression(spEL);
                // spring的表达式上下文对象
                EvaluationContext context = new StandardEvaluationContext();
                // 给上下文赋值
                for (int i = 0; i < args.length; i++) {
                    context.setVariable(paramNames[i], args[i]);
                    params.put(paramNames[i], args[i]);
                }
                return expression.getValue(context, String.class);
            }
        } catch (Exception e) {
            log.error("#{}.{}#分布式锁SpEL表达式|{}|计算失败，加锁失败，args：{}，exception：{}",
                    methodSignature.getDeclaringTypeName(), methodSignature.getName(),
                    spEL, JSONObject.toJSONString(params), e.getMessage());
        }
        return null;
    }
}
